#include <string>
#include <fstream>
#include <iostream>
#include <cstring>
#include <cstdlib>
#include <vector>

#ifdef __APPLE__
#include <OpenGL/OpenGL.h>
#include <GLUT/glut.h>
#else
#include <GL/glut.h>
#endif

#include "imageloader.h"
#include "objloader.h"
#include "vec3f.h"

using namespace std;

Material::Material(string name, Vec3f Ka, Vec3f Kd, Vec3f Ks, float Ns,
			unsigned int texid)
			: _name(name), _Ka(Ka), _Kd(Kd), _Ks(Ks), _Ns(Ns), _texid(texid)
{
}

string Material::name() const
{
	return _name;
}

void Material::useMTL()
{
	cerr << "Using Material (" << name() << ") now." << endl;
	float mat[] = {_Ka.x, _Ka.y, _Ka.z, 1.0f};
	glMaterialfv(GL_FRONT, GL_AMBIENT, mat);
	mat[0] = _Kd.x;
	mat[1] = _Kd.y;
	mat[2] = _Kd.z;
	glMaterialfv(GL_FRONT, GL_DIFFUSE, mat);
	mat[0] = _Ks.x;
	mat[1] = _Ks.y;
	mat[2] = _Ks.z;
	glMaterialfv(GL_FRONT, GL_SPECULAR, mat);
	glMaterialf(GL_FRONT, GL_SHININESS, _Ns);
}

std::vector<Material> Material::loadMTL(ifstream& file, string dir)
{
	vector<Material> v;
	const int MAXLINE = 256;
	char in[MAXLINE];

	string name;
	Vec3f Ka;
	Vec3f Kd;
	Vec3f Ks;
	float Ns;
	unsigned int texid;
	Image *image;

	while (file)
	{
		file.getline(in, MAXLINE);
		char *s;
		s = strtok(in, " ");
		if (!s)
			continue;

		if (strcmp(s,"newmtl") == 0) {
			if (name.size() > 0)
			{
				// Make material out of loaded properties
				v.push_back(Material(name, Ka, Kd, Ks, Ns, texid));
			}
			// Move to next material
			s = strtok(NULL, " ");
			name = string(s);
		} else if (strcmp(s, "Ka") == 0) {
			for (int i = 0; i < 3; i++)
			{
				s = strtok(NULL, " ");
				switch (i)
				{
					case 0:
						Ka.x = atof(s);
						break;
					case 1:
						Ka.y = atof(s);
						break;
					case 2:
						Ka.z = atof(s);
						break;
				}
			}
		} else if (strcmp(s, "Kd") == 0) {
			for (int i = 0; i < 3; i++)
			{
				s = strtok(NULL, " ");
				switch (i)
				{
					case 0:
						Kd.x = atof(s);
						break;
					case 1:
						Kd.y = atof(s);
						break;
					case 2:
						Kd.z = atof(s);
						break;
				}
			}
		} else if (strcmp(s, "Ks") == 0) {
			for (int i = 0; i < 3; i++)
			{
				s = strtok(NULL, " ");
				switch (i)
				{
					case 0:
						Ks.x = atof(s);
						break;
					case 1:
						Ks.y = atof(s);
						break;
					case 2:
						Ks.z = atof(s);
						break;
				}
			}
		} else if (strcmp(s, "Ns") == 0) {
			s = strtok(NULL, " ");
			Ns = atof(s);
		} else if (strcmp(s, "map_Kd") == 0) {
			s = strtok(NULL, " ");
			string bmpfn;
			
			bmpfn += dir;
			bmpfn += s;
			
			cout << bmpfn << endl;
		
			//Load and bind texture here
			
			image = loadBMP(bmpfn.c_str());
			
			if(!image) 
			{
				cerr << "Image texture failed to load" << endl;
				continue;
            		}
            		else
            		{
                 		glGenTextures(1, &texid);
                 		glBindTexture(GL_TEXTURE_2D, texid);
                 		glTexImage2D(GL_TEXTURE_2D,
							0,
							GL_RGB,
							image->width, 
							image->height,
							0,
							GL_RGB,
							GL_UNSIGNED_BYTE,
							image->pixels);               
            		}

			delete image;
			
			glEnable(GL_TEXTURE_2D);
			glBindTexture(GL_TEXTURE_2D, texid);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
			glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
			
			cerr << "UV Texture loaded..." << endl;            
		}
	}
	// Make material out of loaded properties
	if (name.size() > 0)
		v.push_back(Material(name, Ka, Kd, Ks, Ns, texid));

	return v;
}

Model::Model(const char* const filename, unsigned int quality)
{
	string fn(filename);
	switch (quality)
	{
		case 0:
			fn.insert(fn.rfind("/")+1, string("low/"));
			break;
		case 1:
			fn.insert(fn.rfind("/")+1, string("med/"));
			break;
		case 2:
			fn.insert(fn.rfind("/")+1, string("hi/"));
			break;
	}
	cout << fn << endl;
	loadVerts(fn.c_str());
}

void Model::normal(Vec3f a, Vec3f b, Vec3f c, Vec3f vn)
{
	// Two vectors in the triangle's plane
	Vec3f A = b-a;
	Vec3f B = c-a;

	// Cross product
	float norm[] = {A.y * B.z - A.z * B.y,
				A.x * B.z - A.z * B.x,
				A.x * B.y - A.y * B.x};

	// Check if normal is in the right direction
	// (compare to the vertex normal, if dot product
	// is less than zero, flip it)
	//
	// P.S., This doesn't work at all. Looks ugly when
	// it's enabled... normals seem to work with it
	// disabled though...

	float dotprod = norm[0]*vn.x + norm[1]*vn.y + norm[2]*vn.z;
	if (dotprod < 0)
	{
		norm[0] = -norm[0];
		norm[1] = -norm[1];
		norm[2] = -norm[2];
	}

	glNormal3fv(norm);
}

void Model::loadVerts(const char* const filename) 
{
	list<Vec3f> v;
	list<Vec3f> vt;
	list<Vec3f> vn;
	map<string, Material*> mats;


	const int MAXLINE = 256;

	ifstream file;

	{
		string s("../models/");
		s += filename;
		file.open(s.c_str());
	}

	if(!file) 
	{
		cerr << "File failed to load.";
		return;
	}
	
	// C strings are easier to use here...
	char in[MAXLINE];

	// Enter display list compile mode
	dlistid = glGenLists(1);
	glNewList(dlistid, GL_COMPILE);

	while (file)
	{
		file.getline(in, MAXLINE);
		char *s;
		// Split into words, s = first word
		s = strtok(in, " ");

		if (!s)
			continue;

		if (strcmp(s,"mtllib") == 0) {
			s = strtok(NULL, " ");
			ifstream mtlfile;
			vector<Material> m;
			{
				string q("../models/");
				q += filename;
				q = q.substr(0, q.rfind("/")+1);
				string qq(q + s);
				mtlfile.open(qq.c_str());
				m = Material::loadMTL(mtlfile, q);
			}
			for (vector<Material>::const_iterator i = m.begin(); i != m.end(); i++)
			{
				mats[i->name()] = new Material(*i);
			}
			mtlfile.close();
		} else if (strcmp(s, "v") == 0) {
			Vec3f vert;
			for (int i = 0; i < 3; i++)
			{
				s = strtok(NULL, " ");
				switch (i)
				{
					case 0:
						vert.x = atof(s);
						break;
					case 1:
						vert.y = atof(s);
						break;
					case 2:
						vert.z = atof(s);
						break;
				}
			}
			v.push_back(vert);
		} else if (strcmp(s,"vt") == 0) {
			Vec3f vert;
			for (int i = 0; i < 2; i++)
			{
				s = strtok(NULL, " ");
				switch (i)
				{
					case 0:
						vert.x = atof(s);
						break;
					case 1:
						vert.y = atof(s);
						break;
				}
			}
			vt.push_back(vert);
		} else if (strcmp(s,"vn") == 0) {
			Vec3f vert;
			for (int i = 0; i < 2; i++)
			{
				s = strtok(NULL, " ");
				switch (i)
				{
					case 0:
						vert.x = atof(s);
						break;
					case 1:
						vert.y = atof(s);
						break;
					case 2:
						vert.z = atof(s);
						break;
				}
			}
			vn.push_back(vert);
		} else if (strcmp(s,"usemtl") == 0) {
			s = strtok(NULL, " ");
			if (mats.find(string(s)) != mats.end())
			{
				cerr << "Found material. Using..." << endl;
				mats[string(s)] -> useMTL();
			} else {
				cerr << "Can't find material!" << endl;
			}
		} else if (strcmp(s,"f") == 0) {
			// Draw here
			// Compiles into display list
			Vec3f vert[3];
			Vec3f vtex[3];
			Vec3f vnor[3];
			vector<Vec3f> vv;
			for (list<Vec3f>::const_iterator i = v.begin(); i != v.end(); i++)
				vv.push_back(*i);
			vector<Vec3f> vvt;
			for (list<Vec3f>::const_iterator i = vt.begin(); i != vt.end(); i++)
				vvt.push_back(*i);
			vector<Vec3f> vvn;
			for (list<Vec3f>::const_iterator i = vn.begin(); i != vn.end(); i++)
				vvn.push_back(*i);
			// vert 1/vert 2/vert 3
			for (int i = 0; i < 3; i++)
			{
				// vert/tex/norm
				for (int j = 0; j < 3; j++)
				{
					// split by '/'
					s = strtok(NULL, " /");
					int pos = atoi(s);
					pos -= 1;
					switch (j)
					{
						case 0:
							vert[i] = vv[pos];
							break;
						case 1:
							vtex[i] = vvt[pos];
							break;
						case 2:
							vnor[i] = vvn[pos];
							break;
					}
				}
			}
			glBegin(GL_TRIANGLES);
				//Model::normal(vert[0], vert[1],vert[2], vnor[0]);
				for (int i = 0; i < 3; i++)
				{
					glTexCoord2f(vtex[i].x, vtex[i].y);
					glNormal3f(vnor[i].x, vnor[i].y, vnor[i].z);
                    			glVertex3f(vert[i].x, vert[i].y, vert[i].z);
                    			
					
				}
			glEnd();
		}
	}
	// Don't forget this!
	glEndList();
	
	file.close();
	
}

void Model::draw()
{
	glCallList(dlistid);
}
